--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 19cacd0e86d786cb746d17f27added5558c3c945
Parents : ecf13e0
Author : Mark Qvist <mark@unsigned.io>
Date : 2023-12-03T02:08:04+01:00
Implemented background telemetry
Changes
5 files changed, 180 insertions(+), 43 deletions(-)
Diff
diff --git a/sbapp/buildozer.spec b/sbapp/buildozer.spec
index 44962904..08f28e0e 100644
--- a/sbapp/buildozer.spec
+++ b/sbapp/buildozer.spec
@@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,
version.regex = __version__ = ['"](.*)['"]
version.filename = %(source.dir)s/main.py
-android.numeric_version = 20231114
+android.numeric_version = 20231203
# Cryptography recipe is currently broken, using RNS-internal crypto for now
requirements = kivy==2.2.1,libbz2,pillow,qrcode==7.3.1,usb4a,usbserial4a
@@ -25,7 +25,8 @@ android.presplash_color = #00000000
orientation = portrait
fullscreen = 0
-android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_CONNECT,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE
+android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_CONNECT,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE,ACCESS_BACKGROUND_LOCATION
+
android.api = 30
android.minapi = 24
android.ndk = 25b
diff --git a/sbapp/main.py b/sbapp/main.py
index abba9404..d58003bf 100644
--- a/sbapp/main.py
+++ b/sbapp/main.py
@@ -1,6 +1,6 @@
__debug_build__ = False
__disable_shaders__ = False
-__version__ = "0.7.6"
+__version__ = "0.7.7"
__variant__ = "beta"
import sys
diff --git a/sbapp/services/sidebandservice.py b/sbapp/services/sidebandservice.py
index 479e40ad..cdc79dd3 100644
--- a/sbapp/services/sidebandservice.py
+++ b/sbapp/services/sidebandservice.py
@@ -30,8 +30,7 @@ if RNS.vendor.platformutils.get_platform() == "android":
from android import python_act
android_api_version = autoclass('android.os.Build$VERSION').SDK_INT
-
- Context = autoclass('android.content.Context')
+
Intent = autoclass('android.content.Intent')
BitmapFactory = autoclass('android.graphics.BitmapFactory')
Icon = autoclass("android.graphics.drawable.Icon")
@@ -60,6 +59,7 @@ class SidebandService():
0x0483: [0x5740], # ST CDC
0x2E8A: [0x0005, 0x000A], # Raspberry Pi Pico
}
+
def android_notification(self, title="", content="", ticker="", group=None, context_id=None):
if android_api_version < 26:
return
@@ -121,12 +121,78 @@ class SidebandService():
built_notification = notification.build()
self.notification_service.notify(0, built_notification)
+ def check_permission(self, permission):
+ if RNS.vendor.platformutils.is_android():
+ try:
+ result = self.android_service.checkSelfPermission("android.permission."+permission)
+ if result == 0:
+ return True
+
+ except Exception as e:
+ RNS.log("Error while checking permission: "+str(e), RNS.LOG_ERROR)
+
+ return False
+
+ else:
+ return False
+
+ def background_location_allowed(self):
+ if not RNS.vendor.platformutils.is_android():
+ return False
+
+ perms = ["ACCESS_FINE_LOCATION","ACCESS_COARSE_LOCATION","ACCESS_BACKGROUND_LOCATION"]
+ for perm in perms:
+ if not self.check_permission(perm):
+ return False
+
+ return True
+
+ def android_location_callback(self, **kwargs):
+ self._raw_gps = kwargs
+ self._last_gps_update = time.time()
+
+ def should_update_location(self):
+ if self.sideband.config["telemetry_enabled"] and self.sideband.config["telemetry_s_location"] and self.background_location_allowed():
+ return True
+ else:
+ return False
+
+ def update_location_provider(self):
+ if RNS.vendor.platformutils.is_android():
+ if self.should_update_location():
+ if not self._gps_started:
+ RNS.log("Starting service location provider", RNS.LOG_DEBUG)
+
+ if self.gps == None:
+ from plyer import gps
+ self.gps = gps
+
+ self.gps.configure(on_location=self.android_location_callback)
+ self.gps.start(minTime=self._gps_stale_time, minDistance=self._gps_min_distance)
+ self._gps_started = True
+
+ else:
+ if self._gps_started:
+ RNS.log("Stopping service location provider", RNS.LOG_DEBUG)
+ if self.gps != None:
+ self.gps.stop()
+ self._gps_started = False
+ self._raw_gps = None
+
+ def get_location(self):
+ return self._last_gps_update, self._raw_gps
+
def __init__(self):
self.argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
self.app_dir = self.argument
self.multicast_lock = None
self.wake_lock = None
self.should_run = False
+ self.gps = None
+ self._gps_started = False
+ self._gps_stale_time = 299
+ self._gps_min_distance = 25
+ self._raw_gps = None
self.android_service = None
self.app_context = None
@@ -149,9 +215,7 @@ class SidebandService():
self.discover_usb_devices()
- self.sideband = SidebandCore(self, is_service=True, android_app_dir=self.app_dir, verbose=__debug_build__)
- self.sideband.service_context = self.android_service
- self.sideband.owner_service = self
+ self.sideband = SidebandCore(self, is_service=True, android_app_dir=self.app_dir, verbose=__debug_build__, owner_service=self, service_context=self.android_service)
if self.sideband.config["debug"]:
Logger.setLevel(LOG_LEVELS["debug"])
@@ -161,6 +225,7 @@ class SidebandService():
if RNS.vendor.platformutils.is_android():
RNS.log("Discovered USB devices: "+str(self.usb_devices), RNS.LOG_EXTREME)
+ self.update_location_provider()
def discover_usb_devices(self):
diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py
index 84b8c9f6..cc97a2dd 100644
--- a/sbapp/sideband/core.py
+++ b/sbapp/sideband/core.py
@@ -90,6 +90,7 @@ class SidebandCore():
PERIODIC_JOBS_INTERVAL = 60
PERIODIC_SYNC_RETRY = 360
TELEMETRY_INTERVAL = 60
+ SERVICE_TELEMETRY_INTERVAL = 300
IF_CHANGE_ANNOUNCE_MIN_INTERVAL = 6 # In seconds
AUTO_ANNOUNCE_RANDOM_MIN = 90 # In minutes
@@ -103,7 +104,7 @@ class SidebandCore():
# stream logger
self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter)
- def __init__(self, owner_app, is_service=False, is_client=False, android_app_dir=None, verbose=False):
+ def __init__(self, owner_app, is_service=False, is_client=False, android_app_dir=None, verbose=False, owner_service=None, service_context=None):
self.is_service = is_service
self.is_client = is_client
self.db = None
@@ -132,6 +133,8 @@ class SidebandCore():
self.state_lock = Lock()
self.rpc_connection = None
self.service_stopped = False
+ self.service_context = service_context
+ self.owner_service = owner_service
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/sideband"
self.cache_dir = self.app_dir+"/cache"
@@ -2244,6 +2247,23 @@ class SidebandCore():
threading.Thread(target=telemetry_job, daemon=True).start()
+ def run_service_telemetry(self):
+ if not self.telemetry_running:
+ self.telemetry_running = True
+ def telemetry_job():
+ while self.telemetry_running:
+ try:
+ self.update_telemetry()
+ except Exception as e:
+ import traceback
+ exception_info = "".join(traceback.TracebackException.from_exception(e).format())
+ RNS.log(f"An {str(type(e))} occurred while updating service telemetry: {str(e)}", RNS.LOG_ERROR)
+ RNS.log(exception_info, RNS.LOG_ERROR)
+
+ time.sleep(SidebandCore.SERVICE_TELEMETRY_INTERVAL)
+
+ threading.Thread(target=telemetry_job, daemon=True).start()
+
def stop_telemetry(self):
self.telemetry_running = False
self.telemeter.stop_all()
@@ -2298,7 +2318,10 @@ class SidebandCore():
def update_telemeter_config(self):
if self.config["telemetry_enabled"] == True:
if self.telemeter == None:
- self.telemeter = Telemeter()
+ if self.service_context == None:
+ self.telemeter = Telemeter()
+ else:
+ self.telemeter = Telemeter(android_context=self.service_context, service=True, location_provider=self.owner_service)
sensors = ["location", "information", "battery", "pressure", "temperature", "humidity", "magnetic_field", "ambient_light", "gravity", "angular_velocity", "acceleration", "proximity"]
for sensor in sensors:
@@ -2561,6 +2584,8 @@ class SidebandCore():
if self.is_service or self.is_standalone:
while True:
time.sleep(SidebandCore.PERIODIC_JOBS_INTERVAL)
+ self.owner_service.update_location_provider()
+
if self.config["lxmf_periodic_sync"] == True:
if self.getpersistent("lxmf.lastsync") == None:
self.setpersistent("lxmf.lastsync", time.time())
@@ -2660,6 +2685,8 @@ class SidebandCore():
if self.is_standalone or self.is_client:
if self.config["telemetry_enabled"]:
self.run_telemetry()
+ elif self.is_service:
+ self.run_service_telemetry()
def __add_localinterface(self, delay=None):
self.interface_local_adding = True
diff --git a/sbapp/sideband/sense.py b/sbapp/sideband/sense.py
index 8a41f635..53b90bbf 100644
--- a/sbapp/sideband/sense.py
+++ b/sbapp/sideband/sense.py
@@ -40,7 +40,7 @@ class Telemeter():
RNS.log("An error occurred while unpacking telemetry. The contained exception was: "+str(e), RNS.LOG_ERROR)
return None
- def __init__(self, from_packed=False):
+ def __init__(self, from_packed=False, android_context=None, service=False, location_provider=None):
self.sids = {
Sensor.SID_TIME: Time,
Sensor.SID_RECEIVED: Received,
@@ -80,10 +80,15 @@ class Telemeter():
if not self.from_packed:
self.enable("time")
+ self.location_provider = location_provider
+ self.android_context = android_context
+ self.service = service
+
def synthesize(self, sensor):
if sensor in self.available:
if not sensor in self.sensors:
self.sensors[sensor] = self.sids[self.available[sensor]]()
+ self.sensors[sensor]._telemeter = self
self.sensors[sensor].active = True
self.sensors[sensor].synthesized = True
@@ -92,6 +97,7 @@ class Telemeter():
if sensor in self.available:
if not sensor in self.sensors:
self.sensors[sensor] = self.sids[self.available[sensor]]()
+ self.sensors[sensor]._telemeter = self
if not self.sensors[sensor].active:
self.sensors[sensor].start()
@@ -150,6 +156,28 @@ class Telemeter():
return rendered
+ def check_permission(self, permission):
+ if RNS.vendor.platformutils.is_android():
+ if self.android_context != None:
+ try:
+ result = self.android_context.checkSelfPermission("android.permission."+permission)
+ if result == 0:
+ return True
+
+ except Exception as e:
+ RNS.log("Error while checking permission: "+str(e), RNS.LOG_ERROR)
+
+ return False
+
+ else:
+ from android.permissions import check_permission
+ return check_permission("android.permission."+permission)
+
+ else:
+ return False
+
+
+
class Sensor():
SID_NONE = 0x00
SID_TIME = 0x01
@@ -169,6 +197,7 @@ class Sensor():
SID_RECEIVED = 0x10
def __init__(self, sid = None, stale_time = None):
+ self._telemeter = None
self._sid = sid or Sensor.SID_NONE
self._stale_time = stale_time
self._data = None
@@ -238,6 +267,13 @@ class Sensor():
def render(self, relative_to=None):
return None
+ def check_permission(self, permission):
+ if self._telemeter != None:
+ return self._telemeter.check_permission(permission)
+ else:
+ from android.permissions import check_permission
+ return check_permission("android.permission."+permission)
+
class Time(Sensor):
SID = Sensor.SID_TIME
STALE_TIME = 0.1
@@ -587,6 +623,7 @@ class Location(Sensor):
self._last_update = None
self._min_distance = Location.MIN_DISTANCE
self._accuracy_target = Location.ACCURACY_TARGET
+ self._query_method = None
self.latitude = None
self.longitude = None
@@ -619,12 +656,15 @@ class Location(Sensor):
def setup_sensor(self):
if RNS.vendor.platformutils.is_android():
- from android.permissions import request_permissions, check_permission
- if check_permission("android.permission.ACCESS_COARSE_LOCATION") and check_permission("android.permission.ACCESS_FINE_LOCATION"):
- self.gps.configure(on_location=self.android_location_callback)
- self.gps.start(minTime=self._stale_time, minDistance=self._min_distance)
-
+ if self._telemeter and self._telemeter.service:
+ self._location_provider = self._telemeter.location_provider
+ else:
+ if self.check_permission("ACCESS_COARSE_LOCATION") and self.check_permission("ACCESS_FINE_LOCATION"):
+ self.gps.configure(on_location=self.android_location_callback)
+ self.gps.start(minTime=self._stale_time, minDistance=self._min_distance)
+ self._location_provider = self
+
self.update_data()
def teardown_sensor(self):
@@ -669,33 +709,37 @@ class Location(Sensor):
}
elif RNS.vendor.platformutils.is_android():
- if "lat" in self._raw:
- self.latitude = self._raw["lat"]
- if "lon" in self._raw:
- self.longitude = self._raw["lon"]
- if "altitude" in self._raw:
- self.altitude = self._raw["altitude"]
- if "speed" in self._raw:
- self.speed = self._raw["speed"]
- if self.speed < 0:
- self.speed = 0
- if "bearing" in self._raw:
- self.bearing = self._raw["bearing"]
- if "accuracy" in self._raw:
- self.accuracy = self._raw["accuracy"]
- if self.accuracy < 0:
- self.accuracy = 0
-
- if self.accuracy != None and self.accuracy <= self._accuracy_target:
- self.data = {
- "latitude": round(self.latitude, 6),
- "longitude": round(self.longitude, 6),
- "altitude": round(self.altitude, 2),
- "speed": round(self.speed, 2),
- "bearing": round(self.bearing, 2),
- "accuracy": round(self.accuracy, 2),
- "last_update": int(self._last_update),
- }
+ if self._location_provider != None:
+ if self._location_provider != self:
+ self._last_update, self._raw = self._location_provider.get_location()
+
+ if "lat" in self._raw:
+ self.latitude = self._raw["lat"]
+ if "lon" in self._raw:
+ self.longitude = self._raw["lon"]
+ if "altitude" in self._raw:
+ self.altitude = self._raw["altitude"]
+ if "speed" in self._raw:
+ self.speed = self._raw["speed"]
+ if self.speed < 0:
+ self.speed = 0
+ if "bearing" in self._raw:
+ self.bearing = self._raw["bearing"]
+ if "accuracy" in self._raw:
+ self.accuracy = self._raw["accuracy"]
+ if self.accuracy < 0:
+ self.accuracy = 0
+
+ if self.accuracy != None and self.accuracy <= self._accuracy_target:
+ self.data = {
+ "latitude": round(self.latitude, 6),
+ "longitude": round(self.longitude, 6),
+ "altitude": round(self.altitude, 2),
+ "speed": round(self.speed, 2),
+ "bearing": round(self.bearing, 2),
+ "accuracy": round(self.accuracy, 2),
+ "last_update": int(self._last_update),
+ }
except:
self.data = None
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────